﻿using System;
using System.Data;
using System.Collections.Generic;
using CounterServerModel.Exceptions;
using CounterServerModel.Authentication;

namespace CounterServerModel
{
    /// <summary>
    /// A class that gives information about users, accounts, and bills.
    /// </summary>
    public class InformationReader
    {
        private AuthenticationService m_AuthenticationService;

        public InformationReader()
        {
            m_AuthenticationService = new AuthenticationService();
        }

        /// <summary>
        /// Returns information of the given user.
        /// </summary>
        /// <param name="i_AuthenticatedUserCredentials">User's credentials.</param>
        /// <param name="i_UserID">The ID of the user to return the information for.</param>
        /// <returns>Information of the given user.</returns>
        public UserInfo GetUserInformation(AuthenticatedUserCredentials i_AuthenticatedUserCredentials, int i_UserID)
        {
            m_AuthenticationService.ValidateAuthentication(i_AuthenticatedUserCredentials);

            bool allowOperation = false;

            DataRow rowOfSearchingUser = GetRowOfUser(i_AuthenticatedUserCredentials.ID);

            eUserPermissions userPermissions = (eUserPermissions)int.Parse(rowOfSearchingUser["Permissions"].ToString());

            if (i_AuthenticatedUserCredentials.ID == i_UserID)
            {
                if (((userPermissions & eUserPermissions.ReadOwnUserInfo) == eUserPermissions.ReadOwnUserInfo) || ((userPermissions & eUserPermissions.ReadOtherUserInfo) == eUserPermissions.ReadOtherUserInfo))
                {
                    allowOperation = true;
                }
            }
            else
            {
                if ((userPermissions & eUserPermissions.ReadOtherUserInfo) == eUserPermissions.ReadOtherUserInfo)
                {
                    allowOperation = true;
                }
            }

            if (!allowOperation)
            {
                throw new NotEnoughPrivilagesException(i_AuthenticatedUserCredentials.ID);
            }

            DataRow rowOfUserSearchedFor = GetRowOfUser(i_UserID);

            DataTable tableOfAccountsOfUser = DBService.Instance.PerformQuery(string.Format("SELECT AccountID FROM [Accounts-Users] WHERE UserID = '{0}'", i_UserID));

            UserInfo userInfo = UserInfo.CreateFromUserRowAndAccountsColumn(rowOfUserSearchedFor, tableOfAccountsOfUser);

            return userInfo;
        }

        /// <summary>
        /// Returns information of the given user.
        /// </summary>
        /// <param name="i_AuthenticatedUserCredentials">User's credentials.</param>
        /// <param name="i_Username">The Username of the user to return the information for.</param>
        /// <returns>Information of the given user.</returns>
        public UserInfo GetUserInformation(AuthenticatedUserCredentials i_AuthenticatedUserCredentials, string i_Username)
        {
            m_AuthenticationService.ValidateAuthentication(i_AuthenticatedUserCredentials);

            DataTable table = DBService.Instance.PerformQuery(string.Format("SELECT UserID FROM Users WHERE Username = '{0}'", i_Username));
            
            if (table.Rows.Count == 0)
            {
                throw new UsernameNotExistsException(i_Username);
            }

            return GetUserInformation(i_AuthenticatedUserCredentials, int.Parse(table.Rows[0]["UserID"].ToString()));
        }

        /// <summary>
        /// Returns information of the given account.
        /// </summary>
        /// <param name="i_AuthenticatedUserCredentials">User's credentials.</param>
        /// <param name="i_AccountID">The ID of the account to return the information for.</param>
        /// <returns>Information of the given account.</returns>
        public AccountInfo GetAccountInformation(AuthenticatedUserCredentials i_AuthenticatedUserCredentials, int i_AccountID)
        {
            m_AuthenticationService.ValidateAuthentication(i_AuthenticatedUserCredentials);

            bool allowOperation = false;

            DataRow rowOfSearchingUser = GetRowOfUser(i_AuthenticatedUserCredentials.ID);

            eUserPermissions userPermissions = (eUserPermissions)int.Parse(rowOfSearchingUser["Permissions"].ToString());

            if ((userPermissions & eUserPermissions.ReadOtherAccountInfo) == eUserPermissions.ReadOtherAccountInfo)
            {
                allowOperation = true;
            }
            else
            {
                DataTable tableOfAccountsOfUser = DBService.Instance.PerformQuery(string.Format("SELECT AccountID FROM [Accounts-Users] WHERE UserID = '{0}'", i_AuthenticatedUserCredentials.ID));

                bool isAssociated = false;
                for (int i = 0; (i < tableOfAccountsOfUser.Rows.Count) && !isAssociated; i++)
                {
                    if (int.Parse(tableOfAccountsOfUser.Rows[i]["AccountID"].ToString()) == i_AccountID)
                    {
                        isAssociated = true;
                    }
                }

                if (isAssociated && ((userPermissions & eUserPermissions.ReadAssociatedAccountInfo) == eUserPermissions.ReadAssociatedAccountInfo))
                {
                    allowOperation = true;
                }
            }

            if (!allowOperation)
            {
                throw new NotEnoughPrivilagesException(i_AuthenticatedUserCredentials.ID);
            }

            DataRow rowOfAccountSearchedFor = GetRowOfAccount(i_AccountID);

            DataTable tableOfUsersOfAccount = DBService.Instance.PerformQuery(string.Format("SELECT UserID FROM [Accounts-Users] WHERE AccountID = '{0}'", i_AccountID));

            AccountInfo accountInfo = AccountInfo.CreateFromAccountRowAndUsersColumn(rowOfAccountSearchedFor, tableOfUsersOfAccount);

            return accountInfo;
        }

        /// <summary>
        /// Returns information of all the accounts.
        /// </summary>
        /// <param name="i_AuthenticatedUserCredentials">User's credentials.</param>
        /// <returns>Information of all the accounts in a List of AccountInfo Objects.</returns>
        public List<AccountInfo> GetAllAccounts(AuthenticatedUserCredentials i_AuthenticatedUserCredentials)
        {
            m_AuthenticationService.ValidateAuthentication(i_AuthenticatedUserCredentials);

            DataRow rowOfSearchingUser = GetRowOfUser(i_AuthenticatedUserCredentials.ID);

            eUserPermissions userPermissions = (eUserPermissions)int.Parse(rowOfSearchingUser["Permissions"].ToString());

            if (!((userPermissions & eUserPermissions.ReadOtherAccountInfo) == eUserPermissions.ReadOtherAccountInfo))
            {
                throw new NotEnoughPrivilagesException(i_AuthenticatedUserCredentials.ID);
            }

            DataTable tableOfAccounts = DBService.Instance.PerformQuery("SELECT AccountID FROM Accounts;");

            List<AccountInfo> accounts = new List<AccountInfo>();
            
            foreach (DataRow row in tableOfAccounts.Rows)
            {
                accounts.Add(GetAccountInformation(i_AuthenticatedUserCredentials, int.Parse(row["AccountID"].ToString())));
            }

            return accounts;
        }

		/// <summary>
		/// Returns information of all the accounts.
		/// </summary>
		/// <param name="i_AuthenticatedUserCredentials">User's credentials.</param>
		/// <returns>Information of all the accounts in a DataTable.</returns>
		public DataTable GetAccountsDataTable(AuthenticatedUserCredentials i_AuthenticatedUserCredentials)
		{
			m_AuthenticationService.ValidateAuthentication(i_AuthenticatedUserCredentials);

			DataRow rowOfSearchingUser = GetRowOfUser(i_AuthenticatedUserCredentials.ID);

			eUserPermissions userPermissions = (eUserPermissions)int.Parse(rowOfSearchingUser["Permissions"].ToString());

			if (!((userPermissions & eUserPermissions.ReadOtherAccountInfo) == eUserPermissions.ReadOtherAccountInfo))
			{
				throw new NotEnoughPrivilagesException(i_AuthenticatedUserCredentials.ID);
			}

			DataTable tableOfAccounts = DBService.Instance.PerformQuery("SELECT [AccountID] ,[Country] ,[City] ,[Street] ,[House] ,[Apartment] ,[Phone] ,[Longitude] ,[Latitude] FROM Accounts;");

			return tableOfAccounts;
		}

		/// <summary>
		/// Returns information of all the accounts Associated with the user.
		/// </summary>
		/// <param name="i_AuthenticatedUserCredentials">User's credentials.</param>
		/// <param name="i_UserID">User Id to get accounts for</param>
		/// <returns>Information of all the accounts in a DataTable that are associated with the give userid.</returns>
		public DataTable GetUserAssociatedAccounts(AuthenticatedUserCredentials i_AuthenticatedUserCredentials, int i_UserID)
		{
			m_AuthenticationService.ValidateAuthentication(i_AuthenticatedUserCredentials);

			DataRow rowOfSearchingUser = GetRowOfUser(i_AuthenticatedUserCredentials.ID);

			eUserPermissions userPermissions = (eUserPermissions)int.Parse(rowOfSearchingUser["Permissions"].ToString());

			if (!((userPermissions & eUserPermissions.ReadAssociatedAccountInfo) == eUserPermissions.ReadAssociatedAccountInfo))
			{
				throw new NotEnoughPrivilagesException(i_AuthenticatedUserCredentials.ID);
			}

			string query = string.Format(@"
							SELECT 
								AU.[UserID] 
								,A.[AccountID] 
								,A.[Country] 
								,A.[City] 
								,A.[Street] 
								,A.[House] 
								,A.[Apartment] 
								,A.[Longitude] 
								,A.[Latitude]
							FROM [Accounts] as A INNER JOIN [Accounts-Users] AU on (AU.AccountID = A.AccountID)
							WHERE AU.[UserID] = {0}
							",i_UserID);
			DataTable tableOfAccounts = DBService.Instance.PerformQuery(query);

			return tableOfAccounts;
		}


        /// <summary>
        /// Returns information of all the users.
        /// </summary>
        /// <param name="i_AuthenticatedUserCredentials">User's credentials.</param>
        /// <returns>Information of all the users.</returns>
        public List<UserInfo> GetAllUsers(AuthenticatedUserCredentials i_AuthenticatedUserCredentials)
        {
            m_AuthenticationService.ValidateAuthentication(i_AuthenticatedUserCredentials);

            DataRow rowOfSearchingUser = GetRowOfUser(i_AuthenticatedUserCredentials.ID);

            eUserPermissions userPermissions = (eUserPermissions)int.Parse(rowOfSearchingUser["Permissions"].ToString());

            if (!((userPermissions & eUserPermissions.ReadOtherUserInfo) == eUserPermissions.ReadOtherUserInfo))
            {
                throw new NotEnoughPrivilagesException(i_AuthenticatedUserCredentials.ID);
            }

            DataTable tableOfUsers = DBService.Instance.PerformQuery("SELECT UserID FROM Users;");

            List<UserInfo> users = new List<UserInfo>();

            foreach (DataRow row in tableOfUsers.Rows)
            {
                users.Add(GetUserInformation(i_AuthenticatedUserCredentials, int.Parse(row["UserID"].ToString())));
            }

            return users;
        }

		/// <summary>
		/// Returns information of all the users.
		/// </summary>
		/// <param name="i_AuthenticatedUserCredentials">User's credentials.</param>
		/// <returns>Information of all the users in a DataTable.</returns>
		public DataTable GetUsersDataTable(AuthenticatedUserCredentials i_AuthenticatedUserCredentials)
		{
			m_AuthenticationService.ValidateAuthentication(i_AuthenticatedUserCredentials);

			DataRow rowOfSearchingUser = GetRowOfUser(i_AuthenticatedUserCredentials.ID);

			eUserPermissions userPermissions = (eUserPermissions)int.Parse(rowOfSearchingUser["Permissions"].ToString());

			if (!((userPermissions & eUserPermissions.ReadOtherUserInfo) == eUserPermissions.ReadOtherUserInfo))
			{
				throw new NotEnoughPrivilagesException(i_AuthenticatedUserCredentials.ID);
			}

			DataTable tableOfUsers = DBService.Instance.PerformQuery("SELECT [UserID], [Username], [Permissions], [Phone], [MobilePhone], [Email], [Password] FROM Users;");

			return tableOfUsers;
		}

        /// <summary>
        /// Returns true if the user exists, and false otherwise.
        /// </summary>
        /// <param name="i_AuthenticatedUserCredentials">The credentials of the user performing the query.</param>
        /// <param name="i_Username">The Username to look for.</param>
        /// <returns>True if the user exists, and false otherwise.</returns>
        public bool IsUserExists(AuthenticatedUserCredentials i_AuthenticatedUserCredentials, string i_Username)
        {
            m_AuthenticationService.ValidateAuthentication(i_AuthenticatedUserCredentials);

            bool allowOperation = false;

            DataRow rowOfSearchingUser = GetRowOfUser(i_AuthenticatedUserCredentials.ID);

            eUserPermissions userPermissions = (eUserPermissions)int.Parse(rowOfSearchingUser["Permissions"].ToString());

            if ((userPermissions & eUserPermissions.ReadOtherUserInfo) == eUserPermissions.ReadOtherUserInfo)
            {
                allowOperation = true;
            }

            if (!allowOperation)
            {
                throw new NotEnoughPrivilagesException(i_AuthenticatedUserCredentials.ID);
            }

            DataTable tableOfUserSearchedFor = DBService.Instance.PerformQuery(string.Format("SELECT Username FROM Users WHERE Username = '{0}'", i_Username));

            if (tableOfUserSearchedFor.Rows.Count == 0)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Returns true if the account exists, and false otherwise.
        /// </summary>
        /// <param name="i_AuthenticatedUserCredentials">The credentials of the user performing the query.</param>
        /// <param name="i_AccountID">The ID of the account to look for.</param>
        /// <returns>True if the account exists, and false otherwise.</returns>
        public bool IsAccountExists(AuthenticatedUserCredentials i_AuthenticatedUserCredentials, string i_Country, string i_City, string i_Street, int i_House, int? i_Apartment)
        {
            m_AuthenticationService.ValidateAuthentication(i_AuthenticatedUserCredentials);

            DataRow rowOfSearchingUser = GetRowOfUser(i_AuthenticatedUserCredentials.ID);

            eUserPermissions userPermissions = (eUserPermissions)int.Parse(rowOfSearchingUser["Permissions"].ToString());

            if (!((userPermissions & eUserPermissions.ReadOtherAccountInfo) == eUserPermissions.ReadOtherAccountInfo))
            {
                throw new NotEnoughPrivilagesException(i_AuthenticatedUserCredentials.ID);
            }

            string query = string.Format(
                "SELECT AccountID FROM Accounts WHERE Country = '{0}' AND City = '{1}' AND Street = '{2}' AND House = '{3}'{4};",
                i_Country,
                i_City,
                i_Street,
                i_House.ToString(),
                i_Apartment == null ? string.Empty : string.Format(" AND Apartment = '{0}'", i_Apartment));

            DataTable tableOfAccountSearchedFor = DBService.Instance.PerformQuery(query);

            if (tableOfAccountSearchedFor.Rows.Count == 0)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Returns information of the given fault.
        /// </summary>
        /// <param name="i_AuthenticatedUserCredentials">User's credentials.</param>
        /// <param name="i_FaultID">The ID of the fault to return the information for.</param>
        /// <returns>Information of the given fault.</returns>
        public Fault GetFaultInformation(AuthenticatedUserCredentials i_AuthenticatedUserCredentials, int i_FaultID)
        {
            m_AuthenticationService.ValidateAuthentication(i_AuthenticatedUserCredentials);

            DataRow rowOfSearchingUser = GetRowOfUser(i_AuthenticatedUserCredentials.ID);

            eUserPermissions userPermissions = (eUserPermissions)int.Parse(rowOfSearchingUser["Permissions"].ToString());

            if (!((userPermissions & eUserPermissions.ReadFaultInfo) == eUserPermissions.ReadFaultInfo))
            {
                throw new NotEnoughPrivilagesException(i_AuthenticatedUserCredentials.ID);
            }

            DataRow rowOfFaultSearchedFor = GetRowOfFault(i_FaultID);

            return Fault.CreateFromFaultRow(rowOfFaultSearchedFor);
        }

        /// <summary>
        /// Returns information of all the faults.
        /// </summary>
        /// <param name="i_AuthenticatedUserCredentials">User's credentials.</param>
        /// <returns>Information of all the faults.</returns>
		public List<Fault> GetAllOpenFaults(AuthenticatedUserCredentials i_AuthenticatedUserCredentials)
        {
            m_AuthenticationService.ValidateAuthentication(i_AuthenticatedUserCredentials);

            DataRow rowOfSearchingUser = GetRowOfUser(i_AuthenticatedUserCredentials.ID);

            eUserPermissions userPermissions = (eUserPermissions)int.Parse(rowOfSearchingUser["Permissions"].ToString());

            if (!((userPermissions & eUserPermissions.ReadFaultInfo) == eUserPermissions.ReadFaultInfo))
            {
                throw new NotEnoughPrivilagesException(i_AuthenticatedUserCredentials.ID);
            }

			DataTable tableOfFaults = DBService.Instance.PerformQuery("SELECT FaultID FROM Faults WHERE isSolved='false';");

            List<Fault> faults = new List<Fault>();

            foreach (DataRow row in tableOfFaults.Rows)
            {
                faults.Add(GetFaultInformation(i_AuthenticatedUserCredentials, int.Parse(row["FaultID"].ToString())));
            }

            return faults;
        }

		/// <summary>
		/// Returns information of all the faults accoring to AccountID.
		/// </summary>
		/// <param name="i_AuthenticatedUserCredentials">User's credentials.</param>
		/// <param name="i_AccountID">Account ID.</param>
		/// <returns>Information of all the faults for a specific account.</returns>
		public List<Fault> GetAllOpenFaults(AuthenticatedUserCredentials i_AuthenticatedUserCredentials, int i_AccountID)
		{
			m_AuthenticationService.ValidateAuthentication(i_AuthenticatedUserCredentials);

			DataRow rowOfSearchingUser = GetRowOfUser(i_AuthenticatedUserCredentials.ID);

			eUserPermissions userPermissions = (eUserPermissions)int.Parse(rowOfSearchingUser["Permissions"].ToString());

			if (!((userPermissions & eUserPermissions.ReadFaultInfo) == eUserPermissions.ReadFaultInfo))
			{
				throw new NotEnoughPrivilagesException(i_AuthenticatedUserCredentials.ID);
			}

			DataTable tableOfFaults = DBService.Instance.PerformQuery(
				string.Format("SELECT FaultID FROM Faults WHERE AccountID={0} AND isSolved='false';",i_AccountID));
			
			List<Fault> faults = new List<Fault>();

			foreach (DataRow row in tableOfFaults.Rows)
			{
				faults.Add(GetFaultInformation(i_AuthenticatedUserCredentials, int.Parse(row["FaultID"].ToString())));
			}

			return faults;
		}

        private DataRow GetRowOfUser(int i_UserID)
        {
            DataTable table = DBService.Instance.PerformQuery(string.Format("SELECT UserID, Username, Permissions, Phone, MobilePhone, Email, Password FROM Users WHERE UserID = '{0}'", i_UserID));

            if (table.Rows.Count == 0)
            {
                throw new UserIDNotExistsException(i_UserID);
            }

            return table.Rows[0];
        }

        private DataRow GetRowOfAccount(int i_AccountID)
        {
            DataTable table = DBService.Instance.PerformQuery(string.Format("SELECT AccountID, Country, City, Street, House, Apartment, Phone, Longitude, Latitude FROM Accounts WHERE AccountID = '{0}'", i_AccountID));

            if (table.Rows.Count == 0)
            {
                throw new AccountIDNotExistsException(i_AccountID);
            }

            return table.Rows[0];
        }

        private DataRow GetRowOfFault(int i_FaultID)
        {
            DataTable table = DBService.Instance.PerformQuery(string.Format("SELECT FaultID, UserID, AccountID, FaultDate, Description, IsSolved FROM Faults WHERE FaultID = '{0}'", i_FaultID));

            if (table.Rows.Count == 0)
            {
                throw new FaultIDNotExistsException(i_FaultID);
            }

            return table.Rows[0];
        }
    }
}
